// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace SnapObjects.Data
{
    internal class DynamicModelBaseFactory : IModelFactory<IDynamicModelBase>
    {
        private readonly Func<IDynamicModelBase> _defaultCreator;
        private readonly Func<object[], IDynamicModelBase> _creator;
        private readonly DynamicModelBaseAccessor _accessor;
        private readonly DynamicModelMeta _modelMetadata;
        public const int MaxGenericTypes = 30;

        public DynamicModelBaseFactory(DynamicModelMeta metadata)
        {
            _modelMetadata = metadata;

            var types = _modelMetadata.PropertyMap.OrderBy(
                m => m.Value.Index).Select(m => m.Value.DataType).ToArray();

            _defaultCreator = this.CreateDefaultConstructor(types);

            _creator = this.CreateConstructor(types);

            _accessor = new DynamicModelBaseAccessor(this, _modelMetadata);
        }

        public IDynamicModelBase Create()
        {
            return _defaultCreator();
        }

        public IDynamicModelBase Create(params object[] values)
        {
            IDynamicModelBase impl;

            if (values.Length > 0)
            {
                impl = _creator(values);
            }
            else
            {
                impl = _defaultCreator();
            }

            return impl;
        }

        public IModelAccessor<IDynamicModelBase> GetAccessor()
        {
            return _accessor;
        }

        public IModelValidator<IDynamicModelBase> GetValidator()
        {
            return null;
        }

        #region private
        private Func<IDynamicModelBase> CreateDefaultConstructor(Type[] types)
        {
            return Expression.Lambda<Func<IDynamicModelBase>>(this.DefaultConstructorExpression(types)).Compile();
        }

        private Func<object[], IDynamicModelBase> CreateConstructor(Type[] types)
        {
            var paramExp = Expression.Parameter(typeof(object[]));

            return Expression.Lambda<Func<object[], IDynamicModelBase>>(
                this.ConstructorExpression(types, paramExp), paramExp).Compile();
        }

        private Expression DefaultConstructorExpression(Type[] types)
        {
            Expression constructorExpression;

            if (types.Length > MaxGenericTypes)
            {
                var expressions = new List<Expression>();

                for (var i = 0; i < types.Length; i += MaxGenericTypes)
                {
                    var arr = types.Skip(i).Take(MaxGenericTypes).ToArray();

                    var item = this.ParseDefaultConstructorExpression(arr);

                    expressions.Add(item);
                }

                var arrExp = Expression.NewArrayInit(typeof(IDynamicModelBase), expressions);

                var newExp = Expression.New(MultiDynamicModel.Constructor, arrExp);

                constructorExpression = Expression.Convert(newExp, typeof(IDynamicModelBase));
            }
            else
            {
                constructorExpression = this.ParseDefaultConstructorExpression(types);
            }

            return constructorExpression;
        }

        private Expression ConstructorExpression(Type[] types, ParameterExpression paramExp)
        {
            Expression constructorExpression;

            if (types.Length > MaxGenericTypes)
            {
                var expressions = new List<Expression>();

                for (var i = 0; i < types.Length; i += MaxGenericTypes)
                {
                    var arr = types.Skip(i).Take(MaxGenericTypes).ToArray();

                    var item = this.ParseDefaultConstructorExpression(arr);

                    expressions.Add(item);
                }

                var arrExp = Expression.NewArrayInit(typeof(IDynamicModelBase), expressions);

                var newExpression = Expression.New(
                    MultiDynamicModel.Constructor2, new Expression[] { arrExp, paramExp });

                constructorExpression = Expression.Convert(newExpression, typeof(IDynamicModelBase));
            }
            else
            {
                constructorExpression = this.ParseDynamicModelExpression(types, paramExp);
            }

            return constructorExpression;
        }

        private Expression ParseDefaultConstructorExpression(Type[] types)
        {
            var dynamicModelType = this.GetDynamicModelType(types);

            var constructor = dynamicModelType.GetConstructor(Type.EmptyTypes);

            return Expression.Convert(Expression.New(constructor), typeof(IDynamicModelBase));
        }

        private Expression ParseDynamicModelExpression(Type[] types, ParameterExpression paramExp)
        {
            var dynamicModelType = this.GetDynamicModelType(types);

            var constructor = dynamicModelType.GetConstructor(new Type[] { typeof(object[]) });

            var newExpression = Expression.New(constructor, new Expression[] { paramExp });

            var constructorExpression = Expression.Convert(newExpression, typeof(IDynamicModelBase));

            return constructorExpression;
        }

        private Type GetDynamicModelType(Type[] types)
        {
            switch (types.Length)
            {
                case 1:
                    return typeof(DynamicModel<>).MakeGenericType(types);
                case 2:
                    return typeof(DynamicModel<,>).MakeGenericType(types);
                case 3:
                    return typeof(DynamicModel<,,>).MakeGenericType(types);
                case 4:
                    return typeof(DynamicModel<,,,>).MakeGenericType(types);
                case 5:
                    return typeof(DynamicModel<,,,,>).MakeGenericType(types);
                case 6:
                    return typeof(DynamicModel<,,,,,>).MakeGenericType(types);
                case 7:
                    return typeof(DynamicModel<,,,,,,>).MakeGenericType(types);
                case 8:
                    return typeof(DynamicModel<,,,,,,,>).MakeGenericType(types);
                case 9:
                    return typeof(DynamicModel<,,,,,,,,>).MakeGenericType(types);
                case 10:
                    return typeof(DynamicModel<,,,,,,,,,>).MakeGenericType(types);
                case 11:
                    return typeof(DynamicModel<,,,,,,,,,,>).MakeGenericType(types);
                case 12:
                    return typeof(DynamicModel<,,,,,,,,,,,>).MakeGenericType(types);
                case 13:
                    return typeof(DynamicModel<,,,,,,,,,,,,>).MakeGenericType(types);
                case 14:
                    return typeof(DynamicModel<,,,,,,,,,,,,,>).MakeGenericType(types);
                case 15:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 16:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 17:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 18:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 19:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 20:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 21:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 22:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 23:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 24:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 25:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 26:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 27:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 28:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 29:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 30:
                    return typeof(DynamicModel<,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types);
            }

            throw new IndexOutOfRangeException();
        }

        #endregion
    }
}
